{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ef42af46-1f6e-4a02-bc64-a39b4da46ffa",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    },
    "tags": []
   },
   "source": [
    "# Randomized Benchmarking\n",
    "\n",
    "This notebook explains how to perform a full, end-to-end, randomized benchmarking (RB) experiment using the Classiq platform. The notebook is divided into several parts describing the different steps of the workflow: model definition, synthesis, execution, and analysis.\n",
    "\n",
    "## 1) Model Definition\n",
    "Start by defining the model, then the high-level function and its constraints:\n",
    "\n",
    "a) Define the parameters of the problem. In this case, five FunctionParams objects correspond to five different models. This part is hardware-unaware.\n",
    "\n",
    "b) Define hardware settings for the problem. These are the basis gates necessary for execution on IBM Quantum machines, which you do later.\n",
    "\n",
    "c) Create models from the results of the previous steps, adding width, depth, and other constraints. Specifically for RB, `num_of_qubits` determines the width and `num_of_cliffords` determines the depth. The synthesis engine does not make use of these constraints, and they are omitted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "939556c9-e619-4c5e-8dd1-dd1dd50b6268",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-05-07T15:33:25.995355Z",
     "iopub.status.busy": "2024-05-07T15:33:25.994917Z",
     "iopub.status.idle": "2024-05-07T15:33:28.862390Z",
     "shell.execute_reply": "2024-05-07T15:33:28.861639Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from classiq import CustomHardwareSettings, Preferences\n",
    "from classiq.qmod import (\n",
    "    Output,\n",
    "    QArray,\n",
    "    QBit,\n",
    "    allocate,\n",
    "    create_model,\n",
    "    qfunc,\n",
    "    randomized_benchmarking,\n",
    ")\n",
    "\n",
    "# a) Parameter definitions\n",
    "num_of_qubits = 1\n",
    "numbers_of_cliffords = [5, 10, 15, 20, 25]\n",
    "\n",
    "\n",
    "# b) Hardware definitions\n",
    "ibmq_basis_gates = [\"id\", \"rz\", \"sx\", \"x\", \"cx\"]\n",
    "hw_settings = CustomHardwareSettings(basis_gates=ibmq_basis_gates)\n",
    "preferences = Preferences(custom_hardware_settings=hw_settings)\n",
    "\n",
    "\n",
    "# c) Model creation\n",
    "def get_model(num_cliffords):\n",
    "    @qfunc\n",
    "    def main(target: Output[QArray[QBit]]):\n",
    "        allocate(num_of_qubits, target)\n",
    "        randomized_benchmarking(num_cliffords, target)\n",
    "\n",
    "    return create_model(main, preferences=preferences)\n",
    "\n",
    "\n",
    "models = [get_model(num_cliffords) for num_cliffords in numbers_of_cliffords]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d4f17a7-31ed-4e6c-813e-e9c801197708",
   "metadata": {},
   "source": [
    "## 2) Synthesis\n",
    "Synthesize the constructed models using the `synthesize_async` command. This creates a circuit in the Classiq engine's `GeneratedCircuit` format for you to access in different low-level formats. This example shows the `transpiled_qasm` format, which takes into account the basis gates defined in the model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "84566159-0012-4195-a2b0-637e846c4bca",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-05-07T15:33:28.868173Z",
     "iopub.status.busy": "2024-05-07T15:33:28.866708Z",
     "iopub.status.idle": "2024-05-07T15:33:36.077524Z",
     "shell.execute_reply": "2024-05-07T15:33:36.076674Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import asyncio\n",
    "\n",
    "from classiq import synthesize_async\n",
    "\n",
    "\n",
    "async def synthesize_all_models(models):\n",
    "    return await asyncio.gather(*[synthesize_async(model) for model in models])\n",
    "\n",
    "\n",
    "quantum_programs = asyncio.run(synthesize_all_models(models))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d16ea7b-3dae-4482-9214-7ae7b1afdb6e",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 3) Execution\n",
    "When you have the programs you are ready to run. Classiq allows running multiple programs on multiple backends in a single command. You specify the hardware (see details in the [executor user guide](https://docs.classiq.io/latest/reference-manual/platform/executor/index.html ) ). This example runs on IBM Quantum simulators but may be replaced by any hardware with the proper access credentials. For IBM Quantum hardware access, for example, replace `ibmq_access_t` with an API token from [IBMQ's website](https://quantum-computing.ibm.com/) and specify the hardware name in the `backend_name` field of the `BackendPreferences` objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "dc5b0e1e-b87f-4280-97c3-d5366b56f18a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-05-07T15:33:36.081265Z",
     "iopub.status.busy": "2024-05-07T15:33:36.080741Z",
     "iopub.status.idle": "2024-05-07T15:33:47.840172Z",
     "shell.execute_reply": "2024-05-07T15:33:47.839539Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# Execution\n",
    "\n",
    "from itertools import product\n",
    "\n",
    "from classiq import execute_async, set_quantum_program_execution_preferences\n",
    "from classiq.execution import (\n",
    "    ClassiqBackendPreferences,\n",
    "    ClassiqSimulatorBackendNames,\n",
    "    ExecutionPreferences,\n",
    ")\n",
    "\n",
    "ibmq_access_t = None\n",
    "\n",
    "backend_names = (\n",
    "    ClassiqSimulatorBackendNames.SIMULATOR_STATEVECTOR,\n",
    "    ClassiqSimulatorBackendNames.SIMULATOR,\n",
    ")\n",
    "backend_prefs = ClassiqBackendPreferences.batch_preferences(\n",
    "    backend_names=backend_names,\n",
    ")\n",
    "\n",
    "qprogs_with_preferences = list()\n",
    "for qprog, backend_pref in product(quantum_programs, backend_prefs):\n",
    "    preferences = ExecutionPreferences(backend_preferences=backend_pref)\n",
    "    qprogs_with_preferences.append(\n",
    "        set_quantum_program_execution_preferences(qprog, preferences)\n",
    "    )\n",
    "\n",
    "\n",
    "async def execute_program(qprog):\n",
    "    job = await execute_async(qprog)\n",
    "    return await job.result_async()\n",
    "\n",
    "\n",
    "async def execute_all_programs(qprogs):\n",
    "    return await asyncio.gather(*[execute_program(qprog) for qprog in qprogs])\n",
    "\n",
    "\n",
    "results = asyncio.run(execute_all_programs(qprogs_with_preferences))\n",
    "samples_results = [res[0].value for res in results]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d3676d0-6f76-46bb-b4ab-18abd4eebc8b",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "## 4) Analysis\n",
    "The final step is to analyze the RB data. While the last two steps were independent of the problem at hand, this part is RB unique. Start by reordering the data, which is given in a 'batch'. For RB analysis, match a program to the number of Clifford gates it represents, hence the `clifford_number_mapping` variable. Then, reorder the data according to the hardware, calling the `RBAnalysis` class to present the hardware comparison histograms.\n",
    "\n",
    "Note: If the backends are not replaced with real hardware, expect the trivial result of 100% fidelity for both backends."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "acb04522-0c86-4ee3-ab96-4bb31fe3e1d5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-05-07T15:33:47.843018Z",
     "iopub.status.busy": "2024-05-07T15:33:47.842649Z",
     "iopub.status.idle": "2024-05-07T15:33:48.118215Z",
     "shell.execute_reply": "2024-05-07T15:33:48.117547Z"
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from typing import Dict\n",
    "\n",
    "from classiq.analyzer.rb import RBAnalysis, order_executor_data_by_hardware\n",
    "\n",
    "mixed_data = tuple(\n",
    "    zip(\n",
    "        backend_prefs * len(quantum_programs),\n",
    "        numbers_of_cliffords * len(backend_names),\n",
    "        samples_results,\n",
    "    )\n",
    ")\n",
    "rb_analysis_params = order_executor_data_by_hardware(mixed_data=mixed_data)\n",
    "\n",
    "multiple_hardware_data = RBAnalysis(experiments_data=rb_analysis_params)\n",
    "total_data = asyncio.run(multiple_hardware_data.show_multiple_hardware_data_async())\n",
    "fig = multiple_hardware_data.plot_multiple_hardware_results()\n",
    "fig.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  },
  "vscode": {
   "interpreter": {
    "hash": "a07aacdcc8a415e7643a2bc993226848ff70704ebef014f87460de9126b773d0"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
